#!/bin/bash

shopt -s -o nounset

declare -x SCRIPT=${0##*/}                  # nombre del "script"
declare -rx wc="/usr/bin/wc"                # contador de líneas y palabras
declare -rx tail="/usr/bin/tail"
declare -rx sort="/usr/bin/sort"
declare -rx mktemp="/bin/mktemp"

declare -a opciones                         # opciones (baderas) existentes
declare -a argumentos                       # argumentos (si existen) de las opciones
declare -a descripciones                    # descripciones (si existen) de las opciones
declare -a opcionales                       # indica si la opción es opcional

declare descripcion                         # descipción de la herramienta
declare ejemplo                             # ejemplo de uso
declare desc                                # descripción breve

declare -i k=-1                             # indice para moverse en los arreglos

# arreglo con los colores usados y variable para restaurar
declare -a in=("\e[1;37m" "\e[1;32m" "\e[1;34m" "\e[1;36m" "\e[1;31m")
declare fin="\e[0m"


# obtiene las dimensiones de la pantalla
function get_size
{
    set -- `stty size 2>/dev/null` 
    COLUMNAS=${2:-`tput cols 2>/dev/null`} 
    LINEAS=${1:-`tput lines 2>/dev/null`} 
    : ${COLUMNAS:=80} ${LINEAS:=24} 
    export COLUMNAS LINEAS
}


# verifica los parámetros recibidos
function main
{
	# si no se encuentran argumentos, muestra el manual y termina el programa
	if [ $# -eq 0 ]; then
		echo -e "${in[4]}Faltan argumentos$fin\n"
        manual_manual
		exit 1
	fi

	# analiza cada uno de los parámetros
	while [ $# -gt 0 ] ; do
		case $1 in
			-d) shift
			    descripcion="$1" ;;

			-e) shift
			    ejemplo="$1" ;;

			-h) manual_manual
                exit 0 ;;
                
            -n) shift
                desc="$1" ;;

			-o) # verifica que se haya especificado si la opción es obligatoria o no y 
			    # que se especifique el nombre de la bandera
			    if ! [ "$4" = "0" -o "$4" = "1" -o "$2" = "" ]; then
			        echo -e "${in[4]}$1, Argumentos incorrectos$fin\n\n"
			        manual_manual
			        exit 1
			    fi
			    k=$k+1
			    opciones[$k]="$2"
			    argumentos[$k]="$3"
			    opcionales[$k]="$4"
			    descripciones[$k]="$5"
			    shift 4 ;;
			    
            -s) shift
                SCRIPT="$1" ;;

			*)  echo -e "${in[4]}$1, Argumento no válido.$fin\n\n"
			    manual_manual
                exit 1 ;;
		esac
		shift
	done
}


# manual de este "script"
function manual_manual
{
    declare -i j=-1

    desc="formateo de manuales"
    descripcion="formateo de manuales de \"scripts\""

    # nombre del programa que invoca
    j=$j+1
    opciones[$j]="s"
    opcionales[$j]="1"
    argumentos[$j]="<nombre>"
    descripciones[$j]="Nombre del programa que llama a este \"script\"."

    j=$j+1
    opciones[$j]="n"
    argumentos[$j]="<cadena>"
    opcionales[$j]="1"
    descripciones[$j]="Breve descripción del \"script\"."
    
    j=$j+1
    opciones[$j]="d"
    argumentos[$j]="<cadena>"
    opcionales[$j]="0"
    descripciones[$j]="Descripción del comando. Este texto se muestra en la sección DESCRIPCIÓN."

    j=$j+1
    opciones[$j]="o"
    argumentos[$j]="<op> <args> <0|1> <desc>"
    opcionales[$j]="1"
    descripciones[$j]="0 opcional, 1 obligatorio."
    
    j=$j+1
    opciones[$j]="e"
    argumentos[$j]="<cadena>"
    opcionales[$j]="0"
    descripciones[$j]="Ejemplo de ejecución del \"script\"."
    
    j=$j+1
    opciones[$j]="h"
    opcionales[$j]="0"
    argumentos[$j]=""
    descripciones[$j]="Muestra este manual."
    
    # muestra el manual de este script
    k=$((${#opciones[@]}-1))
    manual
}


# manual de usuario, recibo los siguientes parámetros:
# $1: Modo de uso
# $2: Descripción del programa
# $3: Ejemplo de uso
# $4: Arreglo con las opciones
# $5: Arreglo con los argumentos de las opciones
# $6: Arreglo con las descipciones de las opciones
function manual
{
	declare e4="    "						# cuatro espacios en blanco
	declare max_l							# longitud (en caracteres) más grande de todas
	                                        # las opciones, incluyendo sus argumentos
	declare -a longitud						# longitud en caracteres que ocupa cada una 
                                            # de las opciones con sus respectivos argu -
                                            # mentos
    declare uso                             # texto del manual

    # actualiza las dimensiones actuales de la pantalla
    get_size
    
    # verifica la descipción corta
    if [ "$desc" = "" ]; then
        echo -e "${in[4]}El parámetro -n es obligatorio$fin\n\n"
        manual_manual
    fi
    
    # verifica que haya al menos una opción
    if [ $k -eq -1 ]; then
        echo -e "${in[4]}Debes especificar al menos una opción, usa la opción -o$fin\n\n"
        manual_manual
    fi
    
    # nombre y descipción breve
    uso+="${in[0]}NOMBRE: $fin\n$e4${in[3]}$SCRIPT$fin - $desc\n\n"

    # agrega el modo de uso
    uso+="${in[0]}SINOPSIS: $fin\n$e4${in[3]}$SCRIPT$fin"
    
    # agrega las banderas y sus argumentos al modo de uso
    for i in $(seq 0 $k); do
        # opcion opcional
        if [ "${opcionales[$i]}" = "0" ]; then
            uso+=" [-${opciones[$i]} ${argumentos[$i]}]"
        # opción obligatoria
        else
            uso+=" -${opciones[$i]} ${argumentos[$i]}"
        fi
    done
    
    # agrega la descripción (si existe)
    if [ ! "$descripcion" = "" ]; then
        uso+="\n\n${in[0]}DESCRIPCIÓN: $fin\n"
        uso+=$(echo $descripcion |
               fold --width $(($COLUMNAS-4)) --spaces |
               while read linea; do echo "$e4$linea"; done)
    fi

    uso+="\n\n${in[0]}OPCIONES: $fin\n"
    
    # longitud en caracteres que ocupa cada una de las opciones junto con sus argumentos
    for i in $(seq 0 $k); do
    	# si no hay argumento se cuentan sólo los carácteres que tiene la opción
    	if [[ "${argumentos[$i]}" = "" ]]; then
    		longitud[$i]=$(echo "--${opciones[$i]} *" | "$wc" --chars)
    	else
    		longitud[$i]=$(echo "-${opciones[$i]} ${argumentos[$i]} *" | "$wc" --chars)
    	fi
    	
    	# variable temporal que servirá para detectar la longitud más grande
    	tmp+="${longitud[$i]}\n"
    done
    
    # máxima longitud
    max_l=$(echo -e "--\n$tmp" | "$sort" -n | "$tail" -n 1)
    
    # número de columnas que tiene la descripción, este número sirve para acomodar la
    # descripción en filas, respetando la alineación vertical
    ancho_desc=$(($COLUMNAS - $max_l - 4))

    # estructura el texto de salida
    for i in $(seq 0 $((${#opciones[*]}-1))); do
    	# nombre de la opción, agrega un guión medio antes del nombre
        uso+="$e4-${opciones[$i]} "	
        
        # si hay argumentos, se agregan al texto
        if [[ ${argumentos[$i]} != "" ]]; then
            uso+="${in[2]}${argumentos[$i]}$fin"
        fi
        
        # espacios necesarios para que de muestre ordenadamente la salida
        for k in $(seq $(($max_l - ${longitud[$i]}))); do uso+=" "; done
        
        # archivo temporal
        declare tmp=$("$mktemp" -t)
        declare -ix l=1
    
        # separa la descripción en columnas
        echo -e "${descripciones[$i]}" | fold --spaces --width $ancho_desc > "$tmp"

        # toma en cuenta descripciones con más de una línea, acomoda la salida vertical-
        # menate basado en el número contenido en la variable "max_l"
        while read linea; do
        	if [ $l -eq 1 ]; then
        		uso+=" ${in[1]}*$fin $linea\n"
        	else
        		for k in $(seq $(($max_l+4))); do uso+=" "; done
        		uso+="$linea\n"
        	fi
        	((l++))
        done < "$tmp"
        
        rm "$tmp"
    done

    # si se requiere, se muestra un ejemplo de uso
    if [ ! "$ejemplo" = "" ]; then
        uso+="\n${in[0]}EJEMPLO DE USO:$fin\n$e4$ejemplo\n"
    fi
    
    # imprime en pantalla el manual
    echo -e "$uso"
}


# función principal
main "$@"

# usa 
manual

exit 0
